import { IParamVo, ParamValueType } from '../dto/expression.ts';
import { ParamTypeEnum } from '../dto/enumeration.ts';
import * as log from 'https://deno.land/std@0.145.0/log/mod.ts';
import { Sandbox } from 'https://deno.land/x/simple_sandbox@v0.1.1/mod.ts';
import {
  ParamTypeMismatchingError,
  ResultTypeMismatchingError,
} from '../error/expression.ts';
import { parseParamType } from '../util/expression.ts';

const sandbox = new Sandbox();

function buildContext(
  context: Record<string, any>,
  kArr: string[],
  value: ParamValueType,
  index = 0,
) {
  const k = kArr[index];

  if (index === kArr.length - 1) {
    // 表示最后一个
    context[k] = value;
    return;
  }

  const ctx: Record<string, any> = context[k] || {};
  context[k] = ctx;

  buildContext(ctx, kArr, value, index + 1);
}

export class ExpressionEngine {
  private readonly params: IParamVo[];
  private readonly expression: string;
  private readonly resultType: ParamTypeEnum;

  constructor(
    params: IParamVo[],
    expression: string,
    resultType: ParamTypeEnum,
  ) {
    this.params = params.map(({ key, type, value }) => {
      if (parseParamType(value) !== type) {
        throw new ParamTypeMismatchingError(key, type, value);
      }

      return {
        key,
        type,
        value,
      };
    });
    this.expression = expression;
    this.resultType = resultType;
  }

  /**
   * 执行表达式
   */
  async eval(): Promise<ParamValueType> {
    const context: Record<string, any> = {};
    this.params.forEach(({ key, type, value }) =>
      buildContext(context, key.split('.'), value)
    );

    const vars = Object.keys(context).map((key, index) =>
      `${key} = ${JSON.stringify(context[key])}`
    );

    try {
      const evaluation = `export default function() {
  ${vars.length > 0 ? `const ${vars.join(', ')};` : ''}
  return ${this.expression};
}`;
      const result = await sandbox.execute({
        code: evaluation,
        args: [],
        // 超时默认值：500毫秒
        timeout: Number.parseInt(
          Deno.env.get('JIANMU_EXPRESSION_TIMEOUT') || '500',
        ),
      });
      log.info(`evaluation: ${evaluation}`);
      log.info(`result: ${result}; expected type: ${this.resultType}`);

      if (parseParamType(result) !== this.resultType) {
        throw new ResultTypeMismatchingError(this.resultType, result);
      }

      return result;
    } catch (err) {
      log.error(err);
      throw err;
    }
  }
}
